// Copyright (c) 2008-2009 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#include <algorithm>
#include <limits>
#include <string>
#include <utility>
#include <vector>

#include "libcef/common/net/http_header_utils.h"
#include "libcef/common/net_service/net_service_util.h"
#include "libcef/common/request_impl.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/common/content_switches.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/load_flags.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_element_reader.h"
#include "net/base/upload_file_element_reader.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_util.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/referrer_policy.h"
#include "services/network/public/cpp/data_element.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
#include "third_party/blink/public/platform/web_http_body.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_error.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/platform/web_url_request_util.h"
#include "third_party/blink/public/web/web_security_policy.h"

#if defined(OHOS_SCHEME_HANDLER)
#include "base/datashare_uri_utils.h"
#include "base/files/file.h"
#include "net/base/io_buffer.h"
#include "services/network/chunked_data_pipe_upload_data_stream.h"
#include "services/network/data_pipe_element_reader.h"
#endif  // defined(OHOS_SCHEME_HANDLER)

namespace {

const char kCacheControlDirectiveNoCache[] = "no-cache";
const char kCacheControlDirectiveNoStore[] = "no-store";
const char kCacheControlDirectiveOnlyIfCached[] = "only-if-cached";

// Mask of values that configure the cache policy.
const int kURCachePolicyMask =
    (UR_FLAG_SKIP_CACHE | UR_FLAG_ONLY_FROM_CACHE | UR_FLAG_DISABLE_CACHE);

// Returns the cef_urlrequest_flags_t policy specified by the Cache-Control
// request header directives, if any. The directives are case-insensitive and
// some have an optional argument. Multiple directives are comma-separated.
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
// for details.
int GetCacheControlHeaderPolicy(CefRequest::HeaderMap headerMap) {
  std::string line;

  // Extract the Cache-Control header line.
  {
    CefRequest::HeaderMap::const_iterator it = headerMap.begin();
    for (; it != headerMap.end(); ++it) {
      if (base::EqualsCaseInsensitiveASCII(
              it->first.ToString(), net::HttpRequestHeaders::kCacheControl)) {
        line = it->second;
        break;
      }
    }
  }

  int flags = 0;

  if (!line.empty()) {
    HttpHeaderUtils::MakeASCIILower(&line);

    std::vector<base::StringPiece> pieces = base::SplitStringPiece(
        line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
    for (const auto& piece : pieces) {
      if (base::EqualsCaseInsensitiveASCII(piece,
                                           kCacheControlDirectiveNoCache)) {
        flags |= UR_FLAG_SKIP_CACHE;
      } else if (base::EqualsCaseInsensitiveASCII(
                     piece, kCacheControlDirectiveOnlyIfCached)) {
        flags |= UR_FLAG_ONLY_FROM_CACHE;
      } else if (base::EqualsCaseInsensitiveASCII(
                     piece, kCacheControlDirectiveNoStore)) {
        flags |= UR_FLAG_DISABLE_CACHE;
      }
    }
  }

  return flags;
}

// Convert cef_urlrequest_flags_t to blink::WebCachePolicy.
blink::mojom::FetchCacheMode GetFetchCacheMode(int ur_flags) {
  const bool skip_cache{!!(ur_flags & UR_FLAG_SKIP_CACHE)};
  const bool only_from_cache{!!(ur_flags & UR_FLAG_ONLY_FROM_CACHE)};
  const bool disable_cache{!!(ur_flags & UR_FLAG_DISABLE_CACHE)};
  if (only_from_cache && (skip_cache || disable_cache)) {
    // The request will always fail because only_from_cache and
    // skip_cache/disable_cache are mutually exclusive.
    return blink::mojom::FetchCacheMode::kUnspecifiedForceCacheMiss;
  } else if (disable_cache) {
    // This additionally implies the skip_cache behavior.
    return blink::mojom::FetchCacheMode::kNoStore;
  } else if (skip_cache) {
    return blink::mojom::FetchCacheMode::kBypassCache;
  } else if (only_from_cache) {
    return blink::mojom::FetchCacheMode::kOnlyIfCached;
  }
  return blink::mojom::FetchCacheMode::kDefault;
}

// Read |headers| into |map|.
void GetHeaderMap(const net::HttpRequestHeaders& headers,
                  CefRequest::HeaderMap& map) {
  map.clear();

  if (headers.IsEmpty()) {
    return;
  }

  net::HttpRequestHeaders::Iterator it(headers);
  while (it.GetNext()) {
    const std::string& name = it.name();

    // Do not include Referer in the header map.
    if (!base::EqualsCaseInsensitiveASCII(name,
                                          net::HttpRequestHeaders::kReferer)) {
      map.insert(std::make_pair(name, it.value()));
    }
  };
}

// Read |source| into |map|.
void GetHeaderMap(const CefRequest::HeaderMap& source,
                  CefRequest::HeaderMap& map) {
  map.clear();

  CefRequest::HeaderMap::const_iterator it = source.begin();
  for (; it != source.end(); ++it) {
    const CefString& name = it->first;

    // Do not include Referer in the header map.
    if (!base::EqualsCaseInsensitiveASCII(name.ToString(),
                                          net::HttpRequestHeaders::kReferer)) {
      map.insert(std::make_pair(name, it->second));
    }
  }
}

#if defined(OHOS_SCHEME_HANDLER)
// A subclass of net::UploadBytesElementReader which owns
// ResourceRequestBody.
class BytesElementReader : public net::UploadBytesElementReader {
 public:
  BytesElementReader(network::ResourceRequestBody* resource_request_body,
                     const network::DataElementBytes& element)
      : net::UploadBytesElementReader(element.AsStringPiece().data(),
                                      element.AsStringPiece().size()),
        resource_request_body_(resource_request_body) {}

  BytesElementReader(const BytesElementReader&) = delete;
  BytesElementReader& operator=(const BytesElementReader&) = delete;

  ~BytesElementReader() override {}

 private:
  scoped_refptr<network::ResourceRequestBody> resource_request_body_;
};

// A subclass of net::UploadFileElementReader which owns
// ResourceRequestBody.
// This class is necessary to ensure the BlobData and any attached shareable
// files survive until upload completion.
class FileElementReader : public net::UploadFileElementReader {
 public:
  FileElementReader(network::ResourceRequestBody* resource_request_body,
                    base::TaskRunner* task_runner,
                    const network::DataElementFile& element,
                    base::File&& file)
      : net::UploadFileElementReader(task_runner,
                                     std::move(file),
                                     element.path(),
                                     element.offset(),
                                     element.length(),
                                     element.expected_modification_time()),
        resource_request_body_(resource_request_body) {}

  FileElementReader(const FileElementReader&) = delete;
  FileElementReader& operator=(const FileElementReader&) = delete;

  ~FileElementReader() override {}

 private:
  scoped_refptr<network::ResourceRequestBody> resource_request_body_;
};

std::unique_ptr<net::UploadDataStream> CreateUploadDataStream(
    network::ResourceRequestBody* body,
    std::vector<base::File>& opened_files,
    base::SequencedTaskRunner* file_task_runner) {
  // In the case of a chunked upload, there will just be one element.
  if (body->elements()->size() == 1) {
    if (body->elements()->begin()->type() ==
        network::mojom::DataElementDataView::Tag::kChunkedDataPipe) {
      auto& element =
          body->elements_mutable()->at(0).As<network::DataElementChunkedDataPipe>();
      const bool has_null_source = element.read_only_once().value();
      auto upload_data_stream =
          std::make_unique<network::ChunkedDataPipeUploadDataStream>(
              body, element.ReleaseChunkedDataPipeGetter(), has_null_source, true);
      if (element.read_only_once()) {
        upload_data_stream->EnableCache();
      }
      body->elements_mutable()->clear();
      return upload_data_stream;
    }
  }

  auto opened_file = opened_files.begin();
  std::vector<std::unique_ptr<net::UploadElementReader>> element_readers;
  for (const auto& element : *body->elements()) {
    switch (element.type()) {
      case network::mojom::DataElementDataView::Tag::kBytes:
        element_readers.push_back(std::make_unique<BytesElementReader>(
            body, element.As<network::DataElementBytes>()));
        break;
      case network::mojom::DataElementDataView::Tag::kFile:
        DCHECK(opened_file != opened_files.end());
        element_readers.push_back(std::make_unique<FileElementReader>(
            body, file_task_runner, element.As<network::DataElementFile>(),
            std::move(*opened_file++)));
        break;
      case network::mojom::DataElementDataView::Tag::kDataPipe: {
        element_readers.push_back(std::make_unique<network::DataPipeElementReader>(
            body,
            element.As<network::DataElementDataPipe>().CloneDataPipeGetter()));
        break;
      }
      case network::mojom::DataElementDataView::Tag::kChunkedDataPipe: {
        // This shouldn't happen, as the traits logic should ensure that if
        // there's a chunked pipe, there's one and only one element.
        NOTREACHED();
        break;
      }
    }
  }
  DCHECK(opened_file == opened_files.end());

  return std::make_unique<net::ElementsUploadDataStream>(
      std::move(element_readers), body->identifier());
}
#endif  // defined(OHOS_SCHEME_HANDLER)
}  // namespace

#define CHECK_READONLY_RETURN(val)          \
  if (read_only_) {                         \
    DCHECK(false) << "object is read only"; \
    return val;                             \
  }

#define CHECK_READONLY_RETURN_VOID()        \
  if (read_only_) {                         \
    DCHECK(false) << "object is read only"; \
    return;                                 \
  }

#define SETBOOLFLAG(obj, flags, method, FLAG) \
  obj.method((flags & (FLAG)) == (FLAG))

// CefRequest -----------------------------------------------------------------

// static
CefRefPtr<CefRequest> CefRequest::Create() {
  CefRefPtr<CefRequest> request(new CefRequestImpl());
  return request;
}

// CefRequestImpl -------------------------------------------------------------

CefRequestImpl::CefRequestImpl() {
  // Verify that our enum matches Chromium's values.
  static_assert(static_cast<int>(REFERRER_POLICY_LAST_VALUE) ==
                    static_cast<int>(net::ReferrerPolicy::MAX),
                "enum mismatch");

  base::AutoLock lock_scope(lock_);
  Reset();
}

#if defined(OHOS_SCHEME_HANDLER)
CefRefPtr<CefPostDataStream> CefRequestImpl::GetUploadStream() {
  base::AutoLock lock_scope(lock_);
  return postdata_stream_;
}

bool CefRequestImpl::IsRedirect() {
  base::AutoLock lock_scope(lock_);
  return is_redirect_;
}

bool CefRequestImpl::HasUserGesture() {
  base::AutoLock lock_scope(lock_);
  return has_user_gesture_;
}
#endif  // defined(OHOS_SCHEME_HANDLER)

bool CefRequestImpl::IsReadOnly() {
  base::AutoLock lock_scope(lock_);
  return read_only_;
}

CefString CefRequestImpl::GetURL() {
  base::AutoLock lock_scope(lock_);
  return url_.spec();
}

void CefRequestImpl::SetURL(const CefString& url) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  const GURL& new_url = GURL(url.ToString());
  if (url_ != new_url) {
    Changed(kChangedUrl);
    url_ = new_url;
  }
}

void CefRequestImpl::SetFrameUrl(const CefString& frame_url) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  const GURL& frame_gurl = GURL(frame_url.ToString());
  if (frame_url_ != frame_gurl) {
    frame_url_ = frame_gurl;
  }
}

CefString CefRequestImpl::GetFrameUrl() {
  base::AutoLock lock_scope(lock_);
  return frame_url_.spec();
}

CefString CefRequestImpl::GetMethod() {
  base::AutoLock lock_scope(lock_);
  return method_;
}

void CefRequestImpl::SetMethod(const CefString& method) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  const std::string& new_method = method;
  if (method_ != new_method) {
    Changed(kChangedMethod);
    method_ = new_method;
  }
}

void CefRequestImpl::SetReferrer(const CefString& referrer_url,
                                 ReferrerPolicy policy) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  const auto& sanitized_referrer = content::Referrer::SanitizeForRequest(
      url_, content::Referrer(GURL(referrer_url.ToString()),
                              NetReferrerPolicyToBlinkReferrerPolicy(policy)));
  const auto sanitized_policy =
      BlinkReferrerPolicyToNetReferrerPolicy(sanitized_referrer.policy);

  if (referrer_url_ != sanitized_referrer.url ||
      referrer_policy_ != sanitized_policy) {
    Changed(kChangedReferrer);
    referrer_url_ = sanitized_referrer.url;
    referrer_policy_ = sanitized_policy;
  }
}

CefString CefRequestImpl::GetReferrerURL() {
  base::AutoLock lock_scope(lock_);
  return referrer_url_.spec();
}

CefRequestImpl::ReferrerPolicy CefRequestImpl::GetReferrerPolicy() {
  base::AutoLock lock_scope(lock_);
  return referrer_policy_;
}

CefRefPtr<CefPostData> CefRequestImpl::GetPostData() {
  base::AutoLock lock_scope(lock_);
  return postdata_;
}

void CefRequestImpl::SetPostData(CefRefPtr<CefPostData> postData) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  Changed(kChangedPostData);
  postdata_ = postData;
}

void CefRequestImpl::GetHeaderMap(HeaderMap& headerMap) {
  base::AutoLock lock_scope(lock_);
  headerMap = headermap_;
}

void CefRequestImpl::SetHeaderMap(const HeaderMap& headerMap) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  Changed(kChangedHeaderMap);
  ::GetHeaderMap(headerMap, headermap_);
}

CefString CefRequestImpl::GetHeaderByName(const CefString& name) {
  base::AutoLock lock_scope(lock_);

  std::string nameLower = name;
  HttpHeaderUtils::MakeASCIILower(&nameLower);

  auto it = HttpHeaderUtils::FindHeaderInMap(nameLower, headermap_);
  if (it != headermap_.end()) {
    return it->second;
  }

  return CefString();
}

void CefRequestImpl::SetHeaderByName(const CefString& name,
                                     const CefString& value,
                                     bool overwrite) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  const std::string& nameStr = name;

  // Do not include Referer in the header map.
  if (base::EqualsCaseInsensitiveASCII(nameStr,
                                       net::HttpRequestHeaders::kReferer)) {
    return;
  }

  Changed(kChangedHeaderMap);

  // There may be multiple values, so remove any first.
  for (auto it = headermap_.begin(); it != headermap_.end();) {
    if (base::EqualsCaseInsensitiveASCII(it->first.ToString(), nameStr)) {
      if (!overwrite) {
        return;
      }
      it = headermap_.erase(it);
    } else {
      ++it;
    }
  }

  headermap_.insert(std::make_pair(name, value));
}

void CefRequestImpl::Set(const CefString& url,
                         const CefString& method,
                         CefRefPtr<CefPostData> postData,
                         const HeaderMap& headerMap) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  const GURL& new_url = GURL(url.ToString());
  if (url_ != new_url) {
    Changed(kChangedUrl);
    url_ = new_url;
  }
  const std::string& new_method = method;
  if (method_ != new_method) {
    Changed(kChangedMethod);
    method_ = new_method;
  }
  if (postdata_ != postData) {
    Changed(kChangedPostData);
    postdata_ = postData;
  }
  Changed(kChangedHeaderMap);
  ::GetHeaderMap(headerMap, headermap_);
}

int CefRequestImpl::GetFlags() {
  base::AutoLock lock_scope(lock_);
  return flags_;
}

void CefRequestImpl::SetFlags(int flags) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  if (flags_ != flags) {
    Changed(kChangedFlags);
    flags_ = flags;
  }
}

CefString CefRequestImpl::GetFirstPartyForCookies() {
  base::AutoLock lock_scope(lock_);
  return site_for_cookies_.RepresentativeUrl().spec();
}

void CefRequestImpl::SetFirstPartyForCookies(const CefString& url) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  auto new_site = net::SiteForCookies::FromUrl(GURL(url.ToString()));
  if (!new_site.IsEquivalent(site_for_cookies_)) {
    Changed(kChangedSiteForCookies);
    site_for_cookies_ = new_site;
  }
}

CefRequestImpl::ResourceType CefRequestImpl::GetResourceType() {
  base::AutoLock lock_scope(lock_);
  return resource_type_;
}

CefRequestImpl::TransitionType CefRequestImpl::GetTransitionType() {
  base::AutoLock lock_scope(lock_);
  return transition_type_;
}

uint64 CefRequestImpl::GetIdentifier() {
  base::AutoLock lock_scope(lock_);
  return identifier_;
}

#ifdef OHOS_NETWORK_CONNINFO
bool CefRequestImpl::IsMainFrame() {
  base::AutoLock lock_scope(lock_);
  return (destination_ == network::mojom::RequestDestination::kDocument);
}
#endif

void CefRequestImpl::Set(const network::ResourceRequest* request,
                         uint64 identifier) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  Reset();

  url_ = request->url;
  method_ = request->method;
  identifier_ = identifier;

  if (request->referrer.is_valid()) {
    const auto& sanitized_referrer = content::Referrer::SanitizeForRequest(
        request->url,
        content::Referrer(
            request->referrer,
            NetReferrerPolicyToBlinkReferrerPolicy(
                static_cast<cef_referrer_policy_t>(request->referrer_policy))));
    referrer_url_ = sanitized_referrer.url;
    referrer_policy_ =
        BlinkReferrerPolicyToNetReferrerPolicy(sanitized_referrer.policy);
  }

  // Transfer request headers.
  ::GetHeaderMap(request->headers, headermap_);

  // Transfer post data, if any.
  if (request->request_body) {
    postdata_ = CefPostData::Create();
    static_cast<CefPostDataImpl*>(postdata_.get())->Set(*request->request_body);
#if defined(OHOS_SCHEME_HANDLER)
    postdata_stream_ = CefPostDataStream::Create();
    static_cast<CefPostDataStreamImpl*>(postdata_stream_.get())->Set(
        request->request_body.get());
#endif  // defined(OHOS_SCHEME_HANDLER)
  }

  site_for_cookies_ = request->site_for_cookies;

  resource_type_ = static_cast<cef_resource_type_t>(request->resource_type);
  transition_type_ =
      static_cast<cef_transition_type_t>(request->transition_type);
#if defined(OHOS_SCHEME_HANDLER)
  has_user_gesture_ = request->has_user_gesture;
  is_redirect_ = false;
#endif  // defined(OHOS_SCHEME_HANDLER)
}

void CefRequestImpl::Get(network::ResourceRequest* request,
                         bool changed_only) const {
  base::AutoLock lock_scope(lock_);

  if (ShouldSet(kChangedUrl, changed_only)) {
    request->url = url_;
  }

  if (ShouldSet(kChangedMethod, changed_only)) {
    request->method = method_;
  }

  if (ShouldSet(kChangedReferrer, changed_only)) {
    request->referrer = referrer_url_;
    request->referrer_policy =
        static_cast<net::ReferrerPolicy>(referrer_policy_);
  }

  if (ShouldSet(kChangedHeaderMap, changed_only)) {
    net::HttpRequestHeaders headers;
    headers.AddHeadersFromString(HttpHeaderUtils::GenerateHeaders(headermap_));
    request->headers.Swap(&headers);
  }

  if (ShouldSet(kChangedPostData, changed_only)) {
    if (postdata_.get()) {
      request->request_body =
          static_cast<CefPostDataImpl*>(postdata_.get())->GetBody();
    } else if (request->request_body) {
      request->request_body = nullptr;
    }
  }

  if (!site_for_cookies_.IsNull() &&
      ShouldSet(kChangedSiteForCookies, changed_only)) {
    request->site_for_cookies = site_for_cookies_;
  }

  if (ShouldSet(kChangedFlags, changed_only)) {
    int flags = flags_;
    if (!(flags & kURCachePolicyMask)) {
      // Only consider the Cache-Control directives when a cache policy is not
      // explicitly set on the request.
      flags |= GetCacheControlHeaderPolicy(headermap_);
    }

    int net_flags = 0;

    if (flags & UR_FLAG_SKIP_CACHE) {
      net_flags |= net::LOAD_BYPASS_CACHE;
    }
    if (flags & UR_FLAG_ONLY_FROM_CACHE) {
      net_flags |= net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION;
    }
    if (flags & UR_FLAG_DISABLE_CACHE) {
      net_flags |= net::LOAD_DISABLE_CACHE;
    }

    if (!(flags & UR_FLAG_ALLOW_STORED_CREDENTIALS)) {
      // This will disable all credentials including cookies, auth tokens, etc.
      request->credentials_mode = network::mojom::CredentialsMode::kOmit;
    }

    request->load_flags = net_flags;
  }
}

void CefRequestImpl::Set(const net::RedirectInfo& redirect_info) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  url_ = redirect_info.new_url;
  method_ = redirect_info.new_method;
  site_for_cookies_ = redirect_info.new_site_for_cookies;

  const auto& sanitized_referrer = content::Referrer::SanitizeForRequest(
      redirect_info.new_url,
      content::Referrer(GURL(redirect_info.new_referrer),
                        NetReferrerPolicyToBlinkReferrerPolicy(
                            static_cast<cef_referrer_policy_t>(
                                redirect_info.new_referrer_policy))));
  referrer_url_ = sanitized_referrer.url;
  referrer_policy_ =
      BlinkReferrerPolicyToNetReferrerPolicy(sanitized_referrer.policy);
}

void CefRequestImpl::Set(const net::HttpRequestHeaders& headers) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  ::GetHeaderMap(headers, headermap_);
}

void CefRequestImpl::Set(content::NavigationHandle* navigation_handle) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  Reset();

  url_ = navigation_handle->GetURL();
  method_ = navigation_handle->IsPost() ? "POST" : "GET";

  const auto& sanitized_referrer = content::Referrer::SanitizeForRequest(
      navigation_handle->GetURL(), navigation_handle->GetReferrer());
  referrer_url_ = sanitized_referrer->url;
  referrer_policy_ =
      BlinkReferrerPolicyToNetReferrerPolicy(sanitized_referrer->policy);

  resource_type_ =
      navigation_handle->IsInMainFrame() ? RT_MAIN_FRAME : RT_SUB_FRAME;
  transition_type_ = static_cast<cef_transition_type_t>(
      navigation_handle->GetPageTransition());
}

// static
void CefRequestImpl::Get(const cef::mojom::RequestParamsPtr& params,
                         blink::WebURLRequest& request) {
  request.SetUrl(params->url);
  request.SetRequestorOrigin(blink::WebSecurityOrigin::Create(params->url));
  if (!params->method.empty()) {
    request.SetHttpMethod(blink::WebString::FromASCII(params->method));
  }

  if (params->referrer && params->referrer->url.is_valid()) {
    const blink::WebString& referrer =
        blink::WebSecurityPolicy::GenerateReferrerHeader(
            params->referrer->policy, params->url,
            blink::WebString::FromUTF8(params->referrer->url.spec()));
    if (!referrer.IsEmpty()) {
      request.SetReferrerString(referrer);
      request.SetReferrerPolicy(params->referrer->policy);
    }
  }

  CefRequest::HeaderMap headerMap;
  if (!params->headers.empty()) {
    for (net::HttpUtil::HeadersIterator i(params->headers.begin(),
                                          params->headers.end(), "\n\r");
         i.GetNext();) {
      request.AddHttpHeaderField(blink::WebString::FromUTF8(i.name()),
                                 blink::WebString::FromUTF8(i.values()));
      headerMap.insert(std::make_pair(i.name(), i.values()));
    }
  }

  if (params->upload_data) {
    const std::u16string& method = request.HttpMethod().Utf16();
    if (method == u"GET" || method == u"HEAD") {
      request.SetHttpMethod(blink::WebString::FromASCII("POST"));
    }

    // The comparison performed by httpHeaderField() is case insensitive.
    if (request
            .HttpHeaderField(blink::WebString::FromASCII(
                net::HttpRequestHeaders::kContentType))
            .length() == 0) {
      request.SetHttpHeaderField(
          blink::WebString::FromASCII(net::HttpRequestHeaders::kContentType),
          blink::WebString::FromASCII(
              net_service::kContentTypeApplicationFormURLEncoded));
    }

    request.SetHttpBody(
        blink::GetWebHTTPBodyForRequestBody(*params->upload_data));
  }

  if (!params->site_for_cookies.IsNull()) {
    request.SetSiteForCookies(params->site_for_cookies);
  }

  int flags = params->load_flags;
  if (!(flags & kURCachePolicyMask)) {
    // Only consider the Cache-Control directives when a cache policy is not
    // explicitly set on the request.
    flags |= GetCacheControlHeaderPolicy(headerMap);
  }
  request.SetCacheMode(GetFetchCacheMode(flags));

  SETBOOLFLAG(request, params->load_flags, SetAllowStoredCredentials,
              UR_FLAG_ALLOW_STORED_CREDENTIALS);
  SETBOOLFLAG(request, params->load_flags, SetReportUploadProgress,
              UR_FLAG_REPORT_UPLOAD_PROGRESS);
}

void CefRequestImpl::Get(cef::mojom::RequestParamsPtr& params) const {
  base::AutoLock lock_scope(lock_);

  params->url = url_;
  params->method = method_;

  // Referrer policy will be applied later in the request pipeline.
  params->referrer = blink::mojom::Referrer::New(
      referrer_url_, NetReferrerPolicyToBlinkReferrerPolicy(referrer_policy_));

  if (!headermap_.empty()) {
    params->headers = HttpHeaderUtils::GenerateHeaders(headermap_);
  }

  if (postdata_) {
    CefPostDataImpl* impl = static_cast<CefPostDataImpl*>(postdata_.get());
    params->upload_data = impl->GetBody();
  }

  params->site_for_cookies = site_for_cookies_;
  params->load_flags = flags_;
}

void CefRequestImpl::SetReadOnly(bool read_only) {
  base::AutoLock lock_scope(lock_);
  if (read_only_ == read_only) {
    return;
  }

  read_only_ = read_only;

  if (postdata_.get()) {
    static_cast<CefPostDataImpl*>(postdata_.get())->SetReadOnly(read_only);
  }
}

void CefRequestImpl::SetTrackChanges(bool track_changes,
                                     bool backup_on_change) {
  base::AutoLock lock_scope(lock_);
  if (track_changes_ == track_changes) {
    return;
  }

  if (!track_changes && backup_on_change_) {
    backup_.reset();
  }

  track_changes_ = track_changes;
  backup_on_change_ = track_changes ? backup_on_change : false;
  changes_ = kChangedNone;

  if (postdata_.get()) {
    static_cast<CefPostDataImpl*>(postdata_.get())
        ->SetTrackChanges(track_changes);
  }
}

void CefRequestImpl::RevertChanges() {
  base::AutoLock lock_scope(lock_);
  DCHECK(!read_only_);
  DCHECK(track_changes_);
  DCHECK(backup_on_change_);
  if (!backup_) {
    return;
  }

  // Restore the original values if a backup exists.
  if (backup_->backups_ & kChangedUrl) {
    url_ = backup_->url_;
  }
  if (backup_->backups_ & kChangedMethod) {
    method_ = backup_->method_;
  }
  if (backup_->backups_ & kChangedReferrer) {
    referrer_url_ = backup_->referrer_url_;
    referrer_policy_ = backup_->referrer_policy_;
  }
  if (backup_->backups_ & kChangedPostData) {
    postdata_ = backup_->postdata_;
  }
  if (backup_->backups_ & kChangedHeaderMap) {
    DCHECK(backup_->headermap_);
    headermap_.swap(*backup_->headermap_);
  }
  if (backup_->backups_ & kChangedFlags) {
    flags_ = backup_->flags_;
  }
  if (backup_->backups_ & kChangedSiteForCookies) {
    site_for_cookies_ = backup_->site_for_cookies_;
  }

  backup_.reset();
}

void CefRequestImpl::DiscardChanges() {
  base::AutoLock lock_scope(lock_);
  DCHECK(track_changes_);
  DCHECK(backup_on_change_);
  backup_.reset();
}

uint8_t CefRequestImpl::GetChanges() const {
  base::AutoLock lock_scope(lock_);

  uint8_t changes = changes_;
  if (postdata_.get() &&
      static_cast<CefPostDataImpl*>(postdata_.get())->HasChanges()) {
    changes |= kChangedPostData;
  }
  return changes;
}

// From content/child/web_url_loader_impl.cc
// static
network::mojom::ReferrerPolicy
CefRequestImpl::NetReferrerPolicyToBlinkReferrerPolicy(
    cef_referrer_policy_t net_policy) {
  switch (net_policy) {
    case REFERRER_POLICY_CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE:
      return network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade;
    case REFERRER_POLICY_REDUCE_REFERRER_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN:
      return network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin;
    case REFERRER_POLICY_ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN:
      return network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin;
    case REFERRER_POLICY_NEVER_CLEAR_REFERRER:
      return network::mojom::ReferrerPolicy::kAlways;
    case REFERRER_POLICY_ORIGIN:
      return network::mojom::ReferrerPolicy::kOrigin;
    case REFERRER_POLICY_CLEAR_REFERRER_ON_TRANSITION_CROSS_ORIGIN:
      return network::mojom::ReferrerPolicy::kSameOrigin;
    case REFERRER_POLICY_ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE:
      return network::mojom::ReferrerPolicy::kStrictOrigin;
    case REFERRER_POLICY_NO_REFERRER:
      return network::mojom::ReferrerPolicy::kNever;
  }
  DCHECK(false);
  return network::mojom::ReferrerPolicy::kDefault;
}

// static
cef_referrer_policy_t CefRequestImpl::BlinkReferrerPolicyToNetReferrerPolicy(
    network::mojom::ReferrerPolicy blink_policy) {
  switch (blink_policy) {
    case network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade:
      return REFERRER_POLICY_CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
    case network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin:
      return REFERRER_POLICY_REDUCE_REFERRER_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN;
    case network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin:
      return REFERRER_POLICY_ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN;
    case network::mojom::ReferrerPolicy::kAlways:
      return REFERRER_POLICY_NEVER_CLEAR_REFERRER;
    case network::mojom::ReferrerPolicy::kOrigin:
      return REFERRER_POLICY_ORIGIN;
    case network::mojom::ReferrerPolicy::kSameOrigin:
      return REFERRER_POLICY_CLEAR_REFERRER_ON_TRANSITION_CROSS_ORIGIN;
    case network::mojom::ReferrerPolicy::kStrictOrigin:
      return REFERRER_POLICY_ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE;
    case network::mojom::ReferrerPolicy::kNever:
      return REFERRER_POLICY_NO_REFERRER;
    case network::mojom::ReferrerPolicy::kDefault:
      return REFERRER_POLICY_DEFAULT;
  }
  DCHECK(false);
  return REFERRER_POLICY_DEFAULT;
}

void CefRequestImpl::Changed(uint8_t changes) {
  lock_.AssertAcquired();
  if (!track_changes_) {
    return;
  }

  if (backup_on_change_) {
    if (!backup_) {
      backup_.reset(new Backup());
    }

    // Set the backup values if not already set.
    if ((changes & kChangedUrl) && !(backup_->backups_ & kChangedUrl)) {
      backup_->url_ = url_;
      backup_->backups_ |= kChangedUrl;
    }
    if ((changes & kChangedMethod) && !(backup_->backups_ & kChangedMethod)) {
      backup_->method_ = method_;
      backup_->backups_ |= kChangedMethod;
    }
    if ((changes & kChangedReferrer) &&
        !(backup_->backups_ & kChangedReferrer)) {
      backup_->referrer_url_ = referrer_url_;
      backup_->referrer_policy_ = referrer_policy_;
      backup_->backups_ |= kChangedReferrer;
    }
    if ((changes & kChangedPostData) &&
        !(backup_->backups_ & kChangedPostData)) {
      backup_->postdata_ = postdata_;
      backup_->backups_ |= kChangedPostData;
    }
    if ((changes & kChangedHeaderMap) &&
        !(backup_->backups_ & kChangedHeaderMap)) {
      backup_->headermap_.reset(new HeaderMap());
      if (!headermap_.empty()) {
        backup_->headermap_->insert(headermap_.begin(), headermap_.end());
      }
      backup_->backups_ |= kChangedHeaderMap;
    }
    if ((changes & kChangedFlags) && !(backup_->backups_ & kChangedFlags)) {
      backup_->flags_ = flags_;
      backup_->backups_ |= kChangedFlags;
    }
    if ((changes & kChangedSiteForCookies) &&
        !(backup_->backups_ & kChangedSiteForCookies)) {
      backup_->site_for_cookies_ = site_for_cookies_;
      backup_->backups_ |= kChangedSiteForCookies;
    }
  }

  changes_ |= changes;
}

bool CefRequestImpl::ShouldSet(uint8_t changes, bool changed_only) const {
  lock_.AssertAcquired();

  // Always change if changes are not being tracked.
  if (!track_changes_) {
    return true;
  }

  // Always change if changed-only was not requested.
  if (!changed_only) {
    return true;
  }

  // Change if the |changes| bit flag has been set.
  if ((changes_ & changes) == changes) {
    return true;
  }

  if ((changes & kChangedPostData) == kChangedPostData) {
    // Change if the post data object was modified directly.
    if (postdata_.get() &&
        static_cast<CefPostDataImpl*>(postdata_.get())->HasChanges()) {
      return true;
    }
  }

  return false;
}

void CefRequestImpl::Reset() {
  lock_.AssertAcquired();
  DCHECK(!read_only_);

  url_ = GURL();
  method_ = "GET";
  referrer_url_ = GURL();
  referrer_policy_ = REFERRER_POLICY_DEFAULT;
  postdata_ = nullptr;
  headermap_.clear();
  resource_type_ = RT_SUB_RESOURCE;
  transition_type_ = TT_EXPLICIT;
  identifier_ = 0U;
  flags_ = UR_FLAG_NONE;
  site_for_cookies_ = net::SiteForCookies();

  changes_ = kChangedNone;
}

#ifdef OHOS_NETWORK_CONNINFO
void CefRequestImpl::SetDestination(
    network::mojom::RequestDestination destination) {
  base::AutoLock lock_scope(lock_);
  destination_ = destination;
}
#endif
#if defined(OHOS_SCHEME_HANDLER)
// CefPostDataStream ----------------------------------------------------------

// static
CefRefPtr<CefPostDataStream> CefPostDataStream::Create() {
  CefRefPtr<CefPostDataStream> postdata_stream(new CefPostDataStreamImpl());
  return postdata_stream;
}

// CefPostDataStreamImpl ------------------------------------------------------

CefPostDataStreamImpl::CefPostDataStreamImpl() {

}

CefPostDataStreamImpl::~CefPostDataStreamImpl() {
  if (is_data_pipe_ && upload_stream_) {
    content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE, std::move(upload_stream_));
  }
}

void CefPostDataStreamImpl::Reset() {
  read_callback_ = nullptr;
  init_callback_ = nullptr;
}

void CefPostDataStreamImpl::GetChunkedDataPipeGetter(
        network::ResourceRequestBody* body) {
  LOG(INFO) << "scheme_handler get chunked data pip getter initialated_: " << initialated_;
  if (body && upload_stream_ && !initialated_) {
    body->SetToChunkedDataPipe(
            static_cast<network::ChunkedDataPipeUploadDataStream*>(upload_stream_.get())
                ->ReleaseChunkedDataPipeGetter(),
            network::ResourceRequestBody::ReadOnlyOnce(HasNullSource()));
  }
}

void CefPostDataStreamImpl::Set(network::ResourceRequestBody* body) {
  std::vector<base::File> open_files;
  if (body) {
    for (const auto& element : *(body->elements())) {
      if (element.type() == network::DataElement::Tag::kFile) {
        const auto& file_element = element.As<network::DataElementFile>();
        std::string real_path = base::GetRealPath(base::FilePath(file_element.path()));
        LOG(INFO) << "scheme_handler file path: " << file_element.path() << " real path: " << real_path;
        open_files.push_back(base::File(base::FilePath(real_path), base::File::FLAG_OPEN | base::File::FLAG_READ));
      }

      if (element.type() == network::DataElement::Tag::kDataPipe ||
              element.type() == network::DataElement::Tag::kChunkedDataPipe) {
        is_data_pipe_ = true;
      }
    }
    scoped_refptr<base::SequencedTaskRunner> task_runner =
        base::ThreadPool::CreateSequencedTaskRunner(
            {base::MayBlock(), base::TaskPriority::USER_VISIBLE});
    upload_stream_ = CreateUploadDataStream(body, open_files, task_runner.get());
  }
}

void CefPostDataStreamImpl::SetReadCallback(
        CefRefPtr<CefPostDataStreamReadCallback> read_callback) {
  read_callback_ = read_callback;
}

void CefPostDataStreamImpl::OnStreamInitialized(int rv) {
  if (init_callback_) {
    init_callback_->OnInitComplete(rv);
  } else {
    LOG(ERROR) << "scheme_handler_init_callback is nullptr.";
  }
}

void CefPostDataStreamImpl::Init(CefRefPtr<CefPostDataStreamInitCallback> init_callback) {
  initialated_ = true;
  init_callback_ = init_callback;
  if (upload_stream_) {
    int rv = upload_stream_->Init(
          base::BindOnce(&CefPostDataStreamImpl::OnStreamInitialized,
                         base::Unretained(this)),
          net::NetLogWithSource());
    if (rv == net::ERR_IO_PENDING) {
      return;
    }
    OnStreamInitialized(rv);
  } else {
    OnStreamInitialized(-2);
  }
}

void CefPostDataStreamImpl::ReadOnTaskRunner(
        void* buffer,
        int buf_len,
        base::WaitableEvent* event) {
  scoped_refptr<net::WrappedIOBuffer> upload_buffer =
      base::MakeRefCounted<net::WrappedIOBuffer>(
          reinterpret_cast<char*>(buffer));
  if (upload_stream_) {
    int rv = upload_stream_->Read(
        upload_buffer.get(),buf_len,
        base::BindOnce(&CefPostDataStreamImpl::OnStreamRead,
                       base::Unretained(this), upload_buffer, event));
    if (rv == net::ERR_IO_PENDING) {
      last_read_rv_ = rv;
      return;
    }
    last_read_rv_ = rv;
    event->Signal();
  } else {
    last_read_rv_ = -2;
    event->Signal();
  }
}

void CefPostDataStreamImpl::ReadAsync(
    void* buffer,
    int buf_len,
    CefRefPtr<CefPostDataStreamReadCallback> read_callback) {
  scoped_refptr<net::WrappedIOBuffer> upload_buffer =
      base::MakeRefCounted<net::WrappedIOBuffer>(
          reinterpret_cast<char*>(buffer));
  if (upload_stream_) {
    int rv = upload_stream_->Read(
        upload_buffer.get(),buf_len,
        base::BindOnce(&CefPostDataStreamImpl::OnStreamReadAsync,
                       base::Unretained(this), upload_buffer, read_callback));
    if (rv == net::ERR_IO_PENDING) {
      return;
    }
    OnStreamReadAsync(upload_buffer, read_callback, rv);
  } else {
    OnStreamReadAsync(upload_buffer, read_callback, -2);
  }
}

void CefPostDataStreamImpl::Read(
    void* buffer,
    int buf_len,
    CefRefPtr<CefPostDataStreamReadCallback> read_callback) {
  if (base::SequencedTaskRunner::HasCurrentDefault() || IsInMemory() || IsChunked()) {
    ReadAsync(buffer, buf_len, read_callback);
  } else {
    if (sequenced_task_runner_) {
      base::WaitableEvent* completion = new base::WaitableEvent();
      sequenced_task_runner_->PostTask(
           FROM_HERE, base::BindOnce(&CefPostDataStreamImpl::ReadOnTaskRunner,
                                     this, buffer, buf_len, completion));
      completion->Wait();
      delete completion;
      read_callback->OnReadComplete((char*)buffer, last_read_rv_);
    } else {
      read_callback->OnReadComplete((char*)buffer, -2);
    }
  }
}
 
void CefPostDataStreamImpl::OnStreamReadAsync(
    scoped_refptr<net::WrappedIOBuffer> buffer,
    CefRefPtr<CefPostDataStreamReadCallback> read_callback,
    int rv) {
  if (read_callback) {
   read_callback->OnReadComplete(buffer->data(), rv);
  }
}

void CefPostDataStreamImpl::OnStreamRead(
    scoped_refptr<net::WrappedIOBuffer> buffer,
    base::WaitableEvent* event,
    int rv) {
  last_read_rv_ = rv;
  if (event) {
    event->Signal();
  }
}

uint64_t CefPostDataStreamImpl::GetSize() {
  if (upload_stream_) {
    return upload_stream_->size();
  } else {
    LOG(ERROR) << "scheme_handler upload stream is nullptr.";
    return -1;
  }
}

uint64_t CefPostDataStreamImpl::GetPosition() {
  if (upload_stream_) {
    return upload_stream_->position();
  } else {
    LOG(ERROR) << "scheme_handler upload stream is nullptr.";
    return -1;
  }
}

bool CefPostDataStreamImpl::IsChunked() {
  if (upload_stream_) {
    return upload_stream_->is_chunked();
  } else {
    LOG(ERROR) << "scheme_handler upload stream is nullptr.";
    return false;
  }
}

bool CefPostDataStreamImpl::HasNullSource() {
  if (upload_stream_) {
    return upload_stream_->has_null_source();
  } else {
    LOG(ERROR) << "scheme_handler upload stream is nullptr.";
    return false;
  }
}

bool CefPostDataStreamImpl::IsEOF() {
  if (upload_stream_) {
    return upload_stream_->IsEOF();
  } else {
    LOG(ERROR) << "scheme_handler upload stream is nullptr.";
    return false;
  }
}

bool CefPostDataStreamImpl::IsInMemory() {
  if (upload_stream_) {
    return upload_stream_->IsInMemory();
  } else {
    LOG(ERROR) << "scheme_handler upload stream is nullptr.";
    return false;
  }
}
#endif  // defined(OHOS_SCHEME_HANDLER)

// CefPostData ----------------------------------------------------------------

// static
CefRefPtr<CefPostData> CefPostData::Create() {
  CefRefPtr<CefPostData> postdata(new CefPostDataImpl());
  return postdata;
}

// CefPostDataImpl ------------------------------------------------------------

CefPostDataImpl::CefPostDataImpl()
    : read_only_(false),
      has_excluded_elements_(false),
      track_changes_(false),
      has_changes_(false) {}

bool CefPostDataImpl::IsReadOnly() {
  base::AutoLock lock_scope(lock_);
  return read_only_;
}

bool CefPostDataImpl::HasExcludedElements() {
  base::AutoLock lock_scope(lock_);
  return has_excluded_elements_;
}

size_t CefPostDataImpl::GetElementCount() {
  base::AutoLock lock_scope(lock_);
  return elements_.size();
}

void CefPostDataImpl::GetElements(ElementVector& elements) {
  base::AutoLock lock_scope(lock_);
  elements = elements_;
}

bool CefPostDataImpl::RemoveElement(CefRefPtr<CefPostDataElement> element) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN(false);

  ElementVector::iterator it = elements_.begin();
  for (; it != elements_.end(); ++it) {
    if (it->get() == element.get()) {
      elements_.erase(it);
      Changed();
      return true;
    }
  }

  return false;
}

bool CefPostDataImpl::AddElement(CefRefPtr<CefPostDataElement> element) {
  bool found = false;

  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN(false);

  // check that the element isn't already in the list before adding
  ElementVector::const_iterator it = elements_.begin();
  for (; it != elements_.end(); ++it) {
    if (it->get() == element.get()) {
      found = true;
      break;
    }
  }

  if (!found) {
    elements_.push_back(element);
    Changed();
  }

  return !found;
}

void CefPostDataImpl::RemoveElements() {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();
  elements_.clear();
  Changed();
}

void CefPostDataImpl::Set(const network::ResourceRequestBody& body) {
  {
    base::AutoLock lock_scope(lock_);
    CHECK_READONLY_RETURN_VOID();
  }

  CefRefPtr<CefPostDataElement> postelem;

  for (const auto& element : *body.elements()) {
    postelem = CefPostDataElement::Create();
    static_cast<CefPostDataElementImpl*>(postelem.get())->Set(element);
    AddElement(postelem);
  }
}

scoped_refptr<network::ResourceRequestBody> CefPostDataImpl::GetBody() const {
  base::AutoLock lock_scope(lock_);

  scoped_refptr<network::ResourceRequestBody> body =
      new network::ResourceRequestBody();
  for (const auto& element : elements_) {
    static_cast<CefPostDataElementImpl*>(element.get())->Get(*body);
  }
  return body;
}

void CefPostDataImpl::SetReadOnly(bool read_only) {
  base::AutoLock lock_scope(lock_);
  if (read_only_ == read_only) {
    return;
  }

  read_only_ = read_only;

  ElementVector::const_iterator it = elements_.begin();
  for (; it != elements_.end(); ++it) {
    static_cast<CefPostDataElementImpl*>(it->get())->SetReadOnly(read_only);
  }
}

void CefPostDataImpl::SetTrackChanges(bool track_changes) {
  base::AutoLock lock_scope(lock_);
  if (track_changes_ == track_changes) {
    return;
  }

  track_changes_ = track_changes;
  has_changes_ = false;

  ElementVector::const_iterator it = elements_.begin();
  for (; it != elements_.end(); ++it) {
    static_cast<CefPostDataElementImpl*>(it->get())->SetTrackChanges(
        track_changes);
  }
}

bool CefPostDataImpl::HasChanges() const {
  base::AutoLock lock_scope(lock_);
  if (has_changes_) {
    return true;
  }

  ElementVector::const_iterator it = elements_.begin();
  for (; it != elements_.end(); ++it) {
    if (static_cast<CefPostDataElementImpl*>(it->get())->HasChanges()) {
      return true;
    }
  }

  return false;
}

void CefPostDataImpl::Changed() {
  lock_.AssertAcquired();
  if (track_changes_ && !has_changes_) {
    has_changes_ = true;
  }
}

// CefPostDataElement ---------------------------------------------------------

// static
CefRefPtr<CefPostDataElement> CefPostDataElement::Create() {
  CefRefPtr<CefPostDataElement> element(new CefPostDataElementImpl());
  return element;
}

// CefPostDataElementImpl -----------------------------------------------------

CefPostDataElementImpl::CefPostDataElementImpl()
    : type_(PDE_TYPE_EMPTY),
      read_only_(false),
      track_changes_(false),
      has_changes_(false) {
  memset(&data_, 0, sizeof(data_));
}

CefPostDataElementImpl::~CefPostDataElementImpl() {
  Cleanup();
}

bool CefPostDataElementImpl::IsReadOnly() {
  base::AutoLock lock_scope(lock_);
  return read_only_;
}

void CefPostDataElementImpl::SetToEmpty() {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  Cleanup();
  Changed();
}

void CefPostDataElementImpl::SetToFile(const CefString& fileName) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  // Clear any data currently in the element
  Cleanup();

  // Assign the new data
  type_ = PDE_TYPE_FILE;
  cef_string_copy(fileName.c_str(), fileName.length(), &data_.filename);

  Changed();
}

void CefPostDataElementImpl::SetToBytes(size_t size, const void* bytes) {
  base::AutoLock lock_scope(lock_);
  CHECK_READONLY_RETURN_VOID();

  // Clear any data currently in the element
  Cleanup();

  // Assign the new data
  void* data = malloc(size);
  DCHECK(data != nullptr);
  if (data == nullptr) {
    return;
  }

  memcpy(data, bytes, size);

  type_ = PDE_TYPE_BYTES;
  data_.bytes.bytes = data;
  data_.bytes.size = size;

  Changed();
}

CefPostDataElement::Type CefPostDataElementImpl::GetType() {
  base::AutoLock lock_scope(lock_);
  return type_;
}

CefString CefPostDataElementImpl::GetFile() {
  base::AutoLock lock_scope(lock_);
  DCHECK(type_ == PDE_TYPE_FILE);
  CefString filename;
  if (type_ == PDE_TYPE_FILE) {
    filename.FromString(data_.filename.str, data_.filename.length, false);
  }
  return filename;
}

size_t CefPostDataElementImpl::GetBytesCount() {
  base::AutoLock lock_scope(lock_);
  DCHECK(type_ == PDE_TYPE_BYTES);
  size_t size = 0;
  if (type_ == PDE_TYPE_BYTES) {
    size = data_.bytes.size;
  }
  return size;
}

size_t CefPostDataElementImpl::GetBytes(size_t size, void* bytes) {
  base::AutoLock lock_scope(lock_);
  DCHECK(type_ == PDE_TYPE_BYTES);
  size_t rv = 0;
  if (type_ == PDE_TYPE_BYTES) {
    rv = (size < data_.bytes.size ? size : data_.bytes.size);
    memcpy(bytes, data_.bytes.bytes, rv);
  }
  return rv;
}

void CefPostDataElementImpl::Set(const network::DataElement& element) {
  {
    base::AutoLock lock_scope(lock_);
    CHECK_READONLY_RETURN_VOID();
  }

  if (element.type() == network::DataElement::Tag::kBytes) {
    const auto& bytes_element = element.As<network::DataElementBytes>();
    const auto& bytes = bytes_element.bytes();
    SetToBytes(bytes.size(), bytes.data());
  } else if (element.type() == network::DataElement::Tag::kFile) {
    const auto& file_element = element.As<network::DataElementFile>();
    SetToFile(file_element.path().value());
  }
}

void CefPostDataElementImpl::Get(network::ResourceRequestBody& body) const {
  base::AutoLock lock_scope(lock_);

  if (type_ == PDE_TYPE_BYTES) {
    body.AppendBytes(static_cast<char*>(data_.bytes.bytes), data_.bytes.size);
  } else if (type_ == PDE_TYPE_FILE) {
    base::FilePath path = base::FilePath(CefString(&data_.filename));
    body.AppendFileRange(path, 0, std::numeric_limits<uint64_t>::max(),
                         base::Time());
  } else {
    DCHECK(false);
  }
}

void CefPostDataElementImpl::SetReadOnly(bool read_only) {
  base::AutoLock lock_scope(lock_);
  if (read_only_ == read_only) {
    return;
  }

  read_only_ = read_only;
}

void CefPostDataElementImpl::SetTrackChanges(bool track_changes) {
  base::AutoLock lock_scope(lock_);
  if (track_changes_ == track_changes) {
    return;
  }

  track_changes_ = track_changes;
  has_changes_ = false;
}

bool CefPostDataElementImpl::HasChanges() const {
  base::AutoLock lock_scope(lock_);
  return has_changes_;
}

void CefPostDataElementImpl::Changed() {
  lock_.AssertAcquired();
  if (track_changes_ && !has_changes_) {
    has_changes_ = true;
  }
}

void CefPostDataElementImpl::Cleanup() {
  if (type_ == PDE_TYPE_EMPTY) {
    return;
  }

  if (type_ == PDE_TYPE_BYTES) {
    free(data_.bytes.bytes);
  } else if (type_ == PDE_TYPE_FILE) {
    cef_string_clear(&data_.filename);
  }
  type_ = PDE_TYPE_EMPTY;
  memset(&data_, 0, sizeof(data_));
}
